﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using Stauffware.Common.Core;
using Stauffware.Common.Time;
using System.Globalization;
using NUnit.Framework;

namespace UnitTests.Core.Parsing
{
	//#################################################################################
	/// <summary>
	/// Test parameters for all integer parsers.
	/// </summary>

	public class IntTestParams
	{
		// the input text to be parsed
		public string InputText;

		// the options to use during parsing
		public NumberParseOptions Options;

		// the expected Parse.Value
		public object ExpectedValue;

		// the expected error
		public string ExpectedError;

		// the expected Parse.ParsedText
		public string ExpectedParsedText;

		// constructor
		public IntTestParams (string input, NumberParseOptions options, object expValue, 
										string expError, string expParseText)
		{
			Require.Condition((expValue == null) || (expError == null),
								"at least one of expValue and expError must be null");
			this.InputText = input;
			this.Options = options;
			this.ExpectedValue = expValue;
			this.ExpectedError = expError;
			this.ExpectedParsedText = expParseText;
		}

		/// <summary>
		/// Performs type-independent validation on the given parse result.
		/// </summary>
		public void Validate (IParse parse)
		{
			NUnit.Framework.Assert.AreEqual(this.ExpectedParsedText, parse.ParsedText, "ParsedText");
			NUnit.Framework.Assert.AreEqual(this.ExpectedError, parse.ErrorMessage, "ErrorMessage");
			NUnit.Framework.Assert.AreEqual(this.ExpectedValue != null, parse.GotValue, "GotValue");
			NUnit.Framework.Assert.AreEqual(this.ExpectedError != null, parse.IsError, "IsError");
			NUnit.Framework.Assert.AreEqual((this.ExpectedValue == null) && (this.ExpectedError == null), 
																		parse.IsEmpty, "IsEmpty");
		}

		// produce nice display in NUnit GUI
		public override string ToString ()
		{
			if (this._dispStr == null)
			{
				this._dispStr = Chars.LeftDoubleQuote.Unicode + this.InputText + 
								Chars.RightDoubleQuote.Unicode + " " + Chars.RightArrow.Unicode + " ";

				if (this.ExpectedValue != null)
					this._dispStr += this.ExpectedValue.ToString();
				else if (this.ExpectedError != null)
					this._dispStr += "<" + this.ExpectedError + ">";

				this._dispStr += ", " + Chars.LeftDoubleQuote.Unicode + this.ExpectedParsedText + 
								Chars.RightDoubleQuote.Unicode + "; " + this.Options.ToString();
			}
			return this._dispStr;
		}
		private string _dispStr;
	}

	//#################################################################################
	/// <summary>
	/// A list of TestParam objects.  This class contains the code for generating the tests.
	/// </summary>

	public class TestParamsList<T> : List<IntTestParams>
	{
		private readonly MethodInfo TtoStringMethod;	// string T.ToString(string format)
		private readonly Type IntegerType;				// typeof(T)

		//-----------------------------------------------------------------------------
		// constructor

		public TestParamsList (T min, T max, bool isSigned)
		{
			// get the T.ToString(T, string) method
			this.IntegerType = typeof(T);
			this.TtoStringMethod = this.IntegerType.GetMethod("ToString", StringType);
			Require.NotNull(this.TtoStringMethod, "can't find " + this.IntegerType.FullName + ".ToString(string)");

			this.GenerateTests(min, max, isSigned);

			this.AddP("foo", NumberParseOptions.None, null, "");
			this.AddN("foo", NumberParseOptions.ParseEntireInput, "invalid character in number", "f");
		}

		private static readonly Type[] StringType = { typeof(string) };

		//-----------------------------------------------------------------------------
		// generates tests for a selection of values within the given range

		protected void GenerateTests (T min, T max, bool isSigned)
		{
			if (isSigned)
				this.GenerateTests(0, false);
			
			foreach (int value in TestValues)
			{
				this.GenerateTests(value, false);
				if (isSigned)
					this.GenerateTests(-value, true);
			}

			this.GenerateTests(min, isSigned);
			this.GenerateTests(max, false);
		}

		private static readonly int[] TestValues = { 1, 2, 9, 10, 123 };

		//-----------------------------------------------------------------------------
		// generates tests for a single value

		protected void GenerateTests (object value, bool negative)
		{
			NumberParseOptions options = negative ? NumberParseOptions.AllowAnySign : NumberParseOptions.None;

			this.GenerateTest(value, "0", null, options);
			this.GenerateTest(value, "0", null, options | NumberParseOptions.ParseEntireInput);
			this.GenerateTest(value, "00", null, options);
			this.GenerateTest(value, "#,000", null, options | NumberParseOptions.AllowGrouping);
			this.GenerateTest(value, "000", null, options);
			this.GenerateTest(value, "0 ", null, options | NumberParseOptions.AllowTrailingWhite);

			if (!negative)
			{
				this.GenerateTest(value, " 0", null, options | NumberParseOptions.AllowLeadingWhite);
				this.GenerateTest(value, " 0 ", null, options | 
						NumberParseOptions.AllowLeadingWhite | NumberParseOptions.AllowTrailingWhite);
				this.GenerateTest(value, " 0 ", null, options | 
						NumberParseOptions.AllowLeadingWhite | NumberParseOptions.AllowTrailingWhite | NumberParseOptions.ParseEntireInput);
			}
		}

		//-----------------------------------------------------------------------------
		// generates one test

		protected void GenerateTest (object value, string format, string trailingText, NumberParseOptions options)
		{
			// call the T.ToString(format) method
			string input = (string) this.TtoStringMethod.Invoke(Convert.ChangeType(value, this.IntegerType), 
																new object[] {format});

			string expectedParsedText = input;
			if (!string.IsNullOrEmpty(trailingText))
				input += trailingText;
			if (!options.HasFlag(NumberParseOptions.ParseEntireInput))
				expectedParsedText = expectedParsedText.TrimEnd();

			this.AddP(input, options, value, expectedParsedText);
		}

		//-----------------------------------------------------------------------------
		// adds a positive testcase

		public void AddP (string input, NumberParseOptions options, object expValue, string expParseText)
		{
			this.Add(new IntTestParams(input, options, expValue, null, expParseText));
		}

		//-----------------------------------------------------------------------------
		// adds a negative testcase

		public void AddN (string input, NumberParseOptions options, string expError, string expParseText)
		{
			this.Add(new IntTestParams(input, options, null, expError, expParseText));
		}

	}

	//#################################################################################
	/// <summary>
	/// The tests that NUnit can run.
	/// </summary>

	public class IntegerParseTests
	{
		//-----------------------------------------------------------------------------
		// Test methods, one for each supported integer type.
		// This code is all boilerplate.

		[Test, TestCaseSource("Generate_S8")]
		public void Test_S8 (IntTestParams prams)
		{
			SByteParser parser = new SByteParser(prams.Options);
			SByteParse parse = parser.Parse(prams.InputText);
			if (parse.GotValue)
				NUnit.Framework.Assert.AreEqual(Convert.ToSByte(prams.ExpectedValue), parse.Value, "Value");
			prams.Validate(parse);
		}

		[Test, TestCaseSource("Generate_U8")]
		public void Test_U8 (IntTestParams prams)
		{
			ByteParser parser = new ByteParser(prams.Options);
			ByteParse parse = parser.Parse(prams.InputText);
			if (parse.GotValue)
				NUnit.Framework.Assert.AreEqual(Convert.ToByte(prams.ExpectedValue), parse.Value, "Value");
			prams.Validate(parse);
		}

		[Test, TestCaseSource("Generate_S16")]
		public void Test_S16 (IntTestParams prams)
		{
			Int16Parser parser = new Int16Parser(prams.Options);
			Int16Parse parse = parser.Parse(prams.InputText);
			if (parse.GotValue)
				NUnit.Framework.Assert.AreEqual(Convert.ToInt16(prams.ExpectedValue), parse.Value, "Value");
			prams.Validate(parse);
		}

		[Test, TestCaseSource("Generate_U16")]
		public void Test_U16 (IntTestParams prams)
		{
			UInt16Parser parser = new UInt16Parser(prams.Options);
			UInt16Parse parse = parser.Parse(prams.InputText);
			if (parse.GotValue)
				NUnit.Framework.Assert.AreEqual(Convert.ToUInt16(prams.ExpectedValue), parse.Value, "Value");
			prams.Validate(parse);
		}

		[Test, TestCaseSource("Generate_S32")]
		public void Test_S32 (IntTestParams prams)
		{
			Int32Parser parser = new Int32Parser(prams.Options);
			Int32Parse parse = parser.Parse(prams.InputText);
			if (parse.GotValue)
				NUnit.Framework.Assert.AreEqual(Convert.ToInt32(prams.ExpectedValue), parse.Value, "Value");
			prams.Validate(parse);
		}

		[Test, TestCaseSource("Generate_U32")]
		public void Test_U32 (IntTestParams prams)
		{
			UInt32Parser parser = new UInt32Parser(prams.Options);
			UInt32Parse parse = parser.Parse(prams.InputText);
			if (parse.GotValue)
				NUnit.Framework.Assert.AreEqual(Convert.ToUInt32(prams.ExpectedValue), parse.Value, "Value");
			prams.Validate(parse);
		}

		[Test, TestCaseSource("Generate_S64")]
		public void Test_S64 (IntTestParams prams)
		{
			Int64Parser parser = new Int64Parser(prams.Options);
			Int64Parse parse = parser.Parse(prams.InputText);
			if (parse.GotValue)
				NUnit.Framework.Assert.AreEqual(Convert.ToInt64(prams.ExpectedValue), parse.Value, "Value");
			prams.Validate(parse);
		}

		[Test, TestCaseSource("Generate_U64")]
		public void Test_U64 (IntTestParams prams)
		{
			UInt64Parser parser = new UInt64Parser(prams.Options);
			UInt64Parse parse = parser.Parse(prams.InputText);
			if (parse.GotValue)
				NUnit.Framework.Assert.AreEqual(Convert.ToUInt64(prams.ExpectedValue), parse.Value, "Value");
			prams.Validate(parse);
		}

		//-----------------------------------------------------------------------------
		// Testcase generators, one for each supported type.
		// This is all boilerplate code.

		public IEnumerable<IntTestParams> Generate_S8 ()
		{
			return new TestParamsList<sbyte>(sbyte.MinValue, sbyte.MaxValue, true);
		}

		public IEnumerable<IntTestParams> Generate_U8 ()
		{
			return new TestParamsList<byte>(byte.MinValue, byte.MaxValue, false);
		}

		public IEnumerable<IntTestParams> Generate_S16 ()
		{
			return new TestParamsList<short>(short.MinValue, short.MaxValue, true);
		}

		public IEnumerable<IntTestParams> Generate_U16 ()
		{
			return new TestParamsList<ushort>(ushort.MinValue, ushort.MaxValue, false);
		}

		public IEnumerable<IntTestParams> Generate_S32 ()
		{
			return new TestParamsList<int>(int.MinValue, int.MaxValue, true);
		}

		public IEnumerable<IntTestParams> Generate_U32 ()
		{
			return new TestParamsList<uint>(uint.MinValue, uint.MaxValue, false);
		}

		public IEnumerable<IntTestParams> Generate_S64 ()
		{
			return new TestParamsList<long>(long.MinValue, long.MaxValue, true);
		}

		public IEnumerable<IntTestParams> Generate_U64 ()
		{
			return new TestParamsList<ulong>(ulong.MinValue, ulong.MaxValue, false);
		}

		//-----------------------------------------------------------------------------
	}

}
